﻿using com.ptrampert.ObjectContexts;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NHibernate;
using NHibernate.Linq;
using System.Linq.Expressions;

namespace com.ptrampert.ObjectContexts.NHibernate
{
    /// <summary>
    /// Base object context with an underlying NHibernate session.
    /// </summary>
    public abstract class ObjectContextBase : IObjectContext
    {
        protected ISession Session { get; set; }

        /// <summary>
        /// Returns whether or not the context has been disposed.
        /// </summary>
        public bool Disposed { get; private set; }

        /// <summary>
        /// Constructs a new object context.
        /// </summary>
        /// <param name="factory">Factory from which an NHibernate session may be obtained.</param>
        public ObjectContextBase(ISessionFactory factory)
        {
            Session = factory.GetCurrentSession();
            Disposed = false;
        }

        /// <summary>
        /// Get all objects of type T.
        /// </summary>
        /// <typeparam name="T">Type of object to get.</typeparam>
        /// <returns>All objects of type T.</returns>
        public IQueryable<T> All<T>() where T : class
        {
            if (!Session.Transaction.IsActive)
                Session.BeginTransaction();
            return Session.Query<T>();
        }

        /// <summary>
        /// Find the object of type T identified by ids.
        /// </summary>
        /// <typeparam name="T">Type of object to find.</typeparam>
        /// <param name="ids">The desired object's identifying values.</param>
        /// <returns>Object of type T, or null if not found.</returns>
        public T Find<T>(params object[] ids) where T : class
        {
            if (!Session.Transaction.IsActive)
                Session.BeginTransaction();
            return Session.Get<T>(ids);
        }

        /// <summary>
        /// Find all objects that match the predicate.
        /// </summary>
        /// <typeparam name="T">Type of objects to find.</typeparam>
        /// <param name="predicate">Predicate describing the matching condition.</param>
        /// <returns>All objects that match the predicate.</returns>
        public IQueryable<T> Find<T>(Expression<Func<T, bool>> predicate) where T : class
        {
            if (!Session.Transaction.IsActive)
                Session.BeginTransaction();
            return Session.Query<T>().Where(predicate);
        }

        /// <summary>
        /// Update an object.
        /// </summary>
        /// <typeparam name="T">Type of object being updated.</typeparam>
        /// <param name="obj">The object being updated.</param>
        public void Update<T>(T obj) where T : class
        {
            if (!Session.Transaction.IsActive)
                Session.BeginTransaction();
            Session.Merge(obj);
        }

        /// <summary>
        /// Add an object.
        /// </summary>
        /// <typeparam name="T">Type of object being added.</typeparam>
        /// <param name="obj">The object being added.</param>
        public void Add<T>(T obj) where T : class
        {
            if (!Session.Transaction.IsActive)
                Session.BeginTransaction();
            Session.Save(obj);
        }

        /// <summary>
        /// Delete an object.
        /// </summary>
        /// <typeparam name="T">Type of object being deleted.</typeparam>
        /// <param name="obj">Object being deleted.</param>
        public void Delete<T>(T obj) where T : class
        {
            if (!Session.Transaction.IsActive)
                Session.BeginTransaction();
            Session.Delete(obj);
        }

        /// <summary>
        /// Delete an object.
        /// </summary>
        /// <typeparam name="T">Type of object being deleted.</typeparam>
        /// <param name="ids">Identifiers for the object to delete.</param>
        public void Delete<T>(params object[] ids) where T : class
        {
            if (!Session.Transaction.IsActive)
                Session.BeginTransaction();
            T obj = Session.Get<T>(ids);
            if (obj != null)
                Session.Delete(obj);
        }

        /// <summary>
        /// Delete all objects that match the predicate.
        /// </summary>
        /// <typeparam name="T">Type of objects being deleted.</typeparam>
        /// <param name="predicate">Predicate describing the matching condition.</param>
        public void Delete<T>(System.Linq.Expressions.Expression<Func<T, bool>> predicate) where T : class
        {
            if (!Session.Transaction.IsActive)
                Session.BeginTransaction();
            foreach (T obj in Find(predicate))
                Session.Delete(obj);
        }

        /// <summary>
        /// Commit changes.
        /// </summary>
        public void Commit()
        {
            if (Session.Transaction.IsActive)
                Session.Transaction.Commit();
        }

        public Task<IQueryable<T>> AllAsync<T>() where T : class
        {
            return Task.Run(() => All<T>());
        }

        public Task<T> FindAsync<T>(params object[] ids) where T : class
        {
            return Task.Run(() => Find<T>(ids));
        }

        public Task<IQueryable<T>> FindAsync<T>(Expression<Func<T, bool>> predicate) where T : class
        {
            return Task.Run(() => Find(predicate));
        }

        public Task UpdateAsync<T>(T obj) where T : class
        {
            return Task.Run(() => Update(obj));
        }

        public Task AddAsync<T>(T obj) where T : class
        {
            return Task.Run(() => Add(obj));
        }

        public Task DeleteAsync<T>(T obj) where T : class
        {
            return Task.Run(() => Delete(obj));
        }

        public Task DeleteAsync<T>(params object[] ids) where T : class
        {
            return Task.Run(() => Delete<T>(ids));
        }

        public Task DeleteAsync<T>(Expression<Func<T, bool>> predicate) where T : class
        {
            return Task.Run(() => Delete(predicate));
        }

        public Task CommitAsync()
        {
            return Task.Run(() => Commit());
        }

        /// <summary>
        /// Dispose the context.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Dispose the context.
        /// </summary>
        /// <param name="disposing">Whether or not this was called by a call to Dispose()</param>
        protected void Dispose(bool disposing)
        {
            if (!Disposed)
            {
                Session.Dispose();
                Disposed = true;
            }
        }
    }
}
